﻿/* File: http.cpp
* Version : 1.0
* Last modified on 2014 - 03 - 18 14 : 19
* Contributors : Nicklas Wallman (s131448), Olof Ivarsson(s132046), Oscar Ernstsson(s132952), Oscar Pehrson(s131469), Martin Jakobsson(s133082)
*/

#include <stdio.h>
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <string.h>
#include <time.h>
#include <process.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include "config.h"
#include "http.h"
#include "logfile.h"

#define PAGE_404 "<html><head><title>404 Not Found</title></head><body><h1>We're sorry! This page is not available.</h1></body></html>"
#define MIME_TEXT_HTML "text/html"
#define MIME_IMAGE_GIF "image/gif"
#define MIME_IMAGE_PNG "image/png"
#define MIME_IMAGE_JPEG "image/jpeg"
#define MIME_ZIP "application/x-compressed"
#define MIME_OTHERS "text/plain"

static char *getLineFromSocket(SOCKET s, int *bytesRead);
static char *getFile(char *fileName, int *bytesRead);
static char *get404Content(fileInfoT fileInfo);
static char *getHeader(fileInfoT fileInfo);
static char *get200header(fileInfoT fileInfo);
static char *get304header(fileInfoT fileInfo);
static char *get404header(fileInfoT fileInfo);
static char *getFullFilePath(char *fileName);
static char *getMsg(SOCKET s);
static fileContentT getHttpContent(char *fileName, char *conditionalDate);
static char *getFileExtension(char *filename);
static char *getMimeType(char *filename);
static char *parseDate(tm *tmDate);
static char *getServerDate(void);
static tm *getFileModifiedDate(char *fileName);
static char *getConditionalDate(char *msg);
int dateCompare(tm *tmDate, char *conditionalDate);
static char *getLocalDate(void);

extern configT srvConfig;

/*
* Function: *getMsg
* Usage: msg = getMsg(socket)
* --------------------------
* This function gets the message from socket and returns it as a string.
* If it get nothing it close the socket and end the thread
*/
static char *getMsg(SOCKET s)
{

	char bufferRecv[2048];
	char *buffer;
	int size;
	int sizeTotal = 0;
	char *tmpBuffer = 0;

	buffer = NULL;
	while((size = recv(s,bufferRecv,sizeof bufferRecv,0)) > 0)
	{
		buffer = (char*) malloc(sizeTotal + size + 1);
		memcpy(buffer, tmpBuffer, sizeTotal);
		memcpy(buffer + sizeTotal, bufferRecv, size);
		sizeTotal += size;
		buffer[sizeTotal] = '\0';
		free(tmpBuffer);
		tmpBuffer = buffer;

		if(strstr(buffer, "\r\n\r\n") != NULL)
		{
			break;
		}
	}

	if (size <= 0)
	{
		closesocket(s);
		_endthread();
	}

	//		printf("Buffer contains:\n%s", buffer);
	return buffer;
}

/*
* Function: parseHTTP
* Usage:  parseHTTP(socket,&httpRequest);
* --------------------------
*  This function parse the incoming request from socket.
*/
int parseHTTP(SOCKET s, httpRequestT *httpRequest)
{
	char *msg, *fullFilePath, *testRequest;
	httpRequestT tmpHttpRequest;
	int bytesRead;

	bytesRead = 0;
	char fileName[200];
	char protocol[20];
	char command[10];

	FILE *infile;

	if ( (msg = getMsg(s)) != NULL )
	{
		if( (sscanf(msg,"%s ",command)) == 1)
		{
			if(strcmp(command,"GET") == 0)
			{
				if( (sscanf(msg,"%s %s %s \n",command,fileName,protocol)) == 3 )
				{
					if ( strcmp(fileName,"/") == 0 )
					{
						strcpy(fileName, "/index.html");
					}

					tmpHttpRequest.conditionalDate = getConditionalDate(msg);
					tmpHttpRequest.command =  (char *) malloc(strlen(command));
					tmpHttpRequest.fileName = (char *) malloc(strlen(fileName));
					tmpHttpRequest.protocol = (char *) malloc(strlen(protocol));

					if (tmpHttpRequest.command == NULL || tmpHttpRequest.fileName == NULL || tmpHttpRequest.protocol == NULL)
					{
						// out of memory
						perror("System out of memory. Can not proceed.");
						getchar();
					}

					strcpy(tmpHttpRequest.command, command);
					strcpy(tmpHttpRequest.fileName, fileName);
					strcpy(tmpHttpRequest.protocol, protocol);
					
					tmpHttpRequest.requestDate = getLocalDate();
					//printf("ReqDate: %s\n", tmpHttpRequest.requestDate);

					*httpRequest = tmpHttpRequest;

					return 1;
				}
			}
		}
	}

	return 0;
}

/*
* Function: *getConditionalDate
* Usage:  conditionalDate = getConditionalDate(msg);
* --------------------------
* This function finds the conditional-date from msg and returns it as a string.
* If nothing is found it returns NULL
*/
static char *getConditionalDate(char *msg)
{
	char *startPtr, *endPtr, *conditionalDate, *space;
	char value[100];
	char key[20];
	int ok;

	if( (startPtr = strstr(msg,"If-Modified-Since:")) != NULL )
	{
		if( (startPtr = strstr(startPtr," ")) != NULL )
		{
			startPtr++; // Bli av med space.
			if( (endPtr = strstr(startPtr,"\n")) != NULL )
			{
				conditionalDate = (char*) malloc(endPtr-startPtr+1);
				memcpy(conditionalDate,startPtr,endPtr-startPtr-1);
				conditionalDate[endPtr-startPtr-1] = '\0';
				return conditionalDate;
			}
		}
	}
	return NULL;
}

/*
* Function: sendPageToClient
* Usage: bytesSent = sendPageToClient(socket,httpRequest,&returnCode);
* --------------------------
* Sends information to client and returns number of bytes sended.
*/
int sendPageToClient(SOCKET s,httpRequestT httpRequest,int *returnCode)
{
	char *message, *header;
	int headerBytesSent, contentBytesSent;
	fileContentT fileContent;
	fileContent = getHttpContent(httpRequest.fileName, httpRequest.conditionalDate);
	header = getHeader(fileContent.fileInfo);

	printf("Header: %s",header);
	if ( (headerBytesSent = send(s,header,strlen(header),0) ) == SOCKET_ERROR )
	{
		printf("Error sending header to client.\n");
	}

	if ( (contentBytesSent = send(s,fileContent.content,fileContent.fileInfo.fileSize,0) ) == SOCKET_ERROR )
	{
		printf("Error sending content to client.\n");
	}

	*returnCode = fileContent.fileInfo.returncode;

	return headerBytesSent+contentBytesSent;
}

/*
* Function: *getHeader
* Usage: header = getHeader(fileInfo);
* --------------------------
* Returns the header for the corresponding return code.
*/
static char *getHeader(fileInfoT fileInfo)
{
	switch(fileInfo.returncode)
	{
	case HTTP_OK:
		return get200header(fileInfo);
	case HTTP_NOT_MODIFIED:
		return get304header(fileInfo);
	case HTTP_NOT_FOUND:
		return get404header(fileInfo);
	default:
		return "";
	}
}

/*
* Function: *get200header
* Usage: header = getHeader(fileInfo);
* --------------------------
* Returns the 200-header
*/
static char *get200header(fileInfoT fileInfo)
{
	char tmpHeader[500];
	char *header;

	sprintf(tmpHeader, "HTTP/1.1 200 OK\r\nServer: EpicWebserver2000 v0.1\r\nDate: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nLast-Modified: %s\r\nConnection: close\r\n\r\n",getServerDate(), fileInfo.mimeType, fileInfo.fileSize, parseDate(fileInfo.lastModified));
	header = (char*) malloc(strlen(tmpHeader));
	strcpy(header,tmpHeader);

	return header;
}

/*
* Function: *get304header
* Usage: header = getHeader(fileInfo);
* --------------------------
* Returns the 304-header
*/
static char *get304header(fileInfoT fileInfo)
{
	char tmpHeader[500];
	char *header;

	sprintf(tmpHeader, "HTTP/1.1 304 Not Modified\r\nServer: EpicWebserver2000 v0.1\r\nDate: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nLast-Modified: %s\r\nConnection: close\r\n\r\n",getServerDate(), fileInfo.mimeType, fileInfo.fileSize, parseDate(fileInfo.lastModified));
	header = (char*)malloc(strlen(tmpHeader));
	strcpy(header, tmpHeader);

	return header;
}

/*
* Function: *get404header
* Usage: header = getHeader(fileInfo);
* --------------------------
* Returns the 404-header
*/
static char *get404header(fileInfoT fileInfo)
{
	char tmpHeader[500];
	char *header;

	sprintf(tmpHeader,"HTTP/1.1 404 Not Found\r\nServer: EpicWebserver2000 v0.1\r\nDate: %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\n\r\n",getServerDate(), fileInfo.mimeType, fileInfo.fileSize);
	header = (char*) malloc(strlen(tmpHeader));
	strcpy(header,tmpHeader);

	return header;
}

/*
* Function: *getFullFilePath
* Usage: filePath = getFullFilePath(filename);
* --------------------------
*  This function returns the filepath of the file requested.
*/
static char *getFullFilePath(char *fileName)
{
	char *filePath;

	filePath = (char *) malloc( (strlen(srvConfig.webPath)) + (strlen(fileName)) + 1);
	strcpy(filePath,srvConfig.webPath);
	strcat(filePath,fileName);

	return filePath;
}

/*
* Function: *getFileExtension
* Usage: fileExtension = getFileExtension(filename);
* --------------------------
* Searches a filename-string for the file extension and returns it.
*/
static char *getFileExtension(char *filename)
{
	int len;
	char *extension;

	len = strlen(filename);
	extension = (char*) malloc(len);
	while (len >= 1)
	{
		if (filename[len-1] == '.')
		{
			break;
		}
		len--;
	}
	extension = filename+len;

	return extension;
}

/*
* Function: *getMimeType
* Usage: mimeType = getMimeType(filename);
* --------------------------
* Takes the file extension and returns the corresponding MIME type.
*/
static char *getMimeType(char *fileName)
{
	char *fileExtension, *mimeType;

	fileExtension = getFileExtension(fileName);
	if(strcmp(fileExtension,"html") == 0 || strcmp(fileExtension,"htm") == 0)
	{
		mimeType = MIME_TEXT_HTML;
	}
	else if(strcmp(fileExtension,"jpeg") == 0 || strcmp(fileExtension,"jpg") == 0)
	{
		mimeType = MIME_IMAGE_JPEG;
	}
	else if(strcmp(fileExtension,"png") == 0)
	{
		mimeType = MIME_IMAGE_PNG;
	}
	else if(strcmp(fileExtension,"gif") == 0)
	{
		mimeType = MIME_IMAGE_GIF;
	}
	else if(strcmp(fileExtension,"zip") == 0)
	{
		mimeType = MIME_ZIP;
	}
	else
	{
		mimeType = MIME_OTHERS;
	}

	return mimeType;
}

/*
* Function: *getFile
* Usage: content = getFile(fileName,&bytesRead);
* --------------------------
* Gets the file requested. If the file is not found, returns NULL.
*/
static char *getFile(char *fileName, int *bytesRead)
{
	char *filePath, *fileExtension,  *buf, *tmpbuf;
	int ch, currMemSize, n;
	FILE *infile;

	n = 0;
	currMemSize = 100;
	filePath = getFullFilePath(fileName);
	fileExtension = getFileExtension(fileName);

	if( (strcmp(fileExtension,"html") == 0) || (strcmp(fileExtension,"htm") == 0) || (strcmp(fileExtension,"txt") == 0) )
		infile = fopen(filePath,"r");
	else
		infile = fopen(filePath,"rb");

	buf = (char *) malloc(currMemSize);
	if( infile != NULL )
	{
		while ( (ch = fgetc(infile)) != EOF)
		{
			buf[n] = ch;
			n++;

			// we need more memory
			if (currMemSize == n)
			{
				tmpbuf = (char *)malloc(currMemSize * 2 + 1);
				memcpy(tmpbuf, buf, currMemSize);
				currMemSize *= 2;
				free(buf);
				buf = tmpbuf;
			}
		}

		fclose(infile);
	}
	else
		return NULL;

	if (n == 0)
	{
		free(buf);
		return (NULL);
	}

	buf[n++] = '\0';
	*bytesRead = n;

	return buf;
}

/*
* Function: *get404Content
* Usage: content = get404Content(&bytesRead);
* --------------------------
* Returns 404 page content.
*/
static char *get404Content(int *contentLength)
{
	char *fileContent;
	int tmpLength;

	if( (fileContent = getFile("/404.html",&tmpLength)) == NULL)
	{
		fileContent = PAGE_404;
		tmpLength = strlen(fileContent);
	}

	*contentLength = tmpLength;

	return fileContent;
}

/*
* Function: getHttpContent
* Usage: fileContent = getHttpContent(filename,&conditionalDate)
* --------------------------
* Returns the content of the HTTP request and checks if its up to date.
*/
static fileContentT getHttpContent(char *fileName, char *conditionalDate)
{
	fileContentT fileContent;
	int bytesRead;

	if( (fileContent.content = getFile(fileName, &bytesRead)) == NULL)
	{
		fileContent.content = get404Content(&bytesRead);
		fileContent.fileInfo.returncode = HTTP_NOT_FOUND;
		fileContent.fileInfo.mimeType = MIME_TEXT_HTML;
		fileContent.fileInfo.lastModified = 0;
		fileContent.fileInfo.fileSize = bytesRead;

	}
	else
	{
		fileContent.fileInfo.lastModified = getFileModifiedDate(fileName);
		if (dateCompare(fileContent.fileInfo.lastModified, conditionalDate))
		{
			fileContent.fileInfo.returncode = HTTP_NOT_MODIFIED;
			fileContent.fileInfo.fileSize = 0;

			free(fileContent.content);
			fileContent.content = NULL;

		}
		else
		{
			fileContent.fileInfo.returncode = HTTP_OK;
			fileContent.fileInfo.fileSize = bytesRead;

		}

		fileContent.fileInfo.mimeType = getMimeType(fileName);
	}


	return fileContent;
}

/*
* Function: *getFileModifiedDate
* Usage:  tmDate = getFileModifiedDate(fileName);
* --------------------------
* Takes a filename and returns the date it was last modified.
*/
static tm *getFileModifiedDate(char *fileName)
{
	char *filePath;

	filePath = getFullFilePath(fileName);
	struct stat buf;
	if (!stat(filePath, &buf))
	{
		free(filePath);
		return gmtime(&buf.st_mtime); 
	}
	else
	{
		free(filePath);
		return NULL;
	}
}

/*
* Function: *parseDate
* Usage:   date = parseDate(tmDate);
* --------------------------
* Takes a tm struct and parse it into a string and returns it.
*/
static char *parseDate(tm *tmDate)
{
	char buf[100];
	char *date;
	size_t size;

	size = strftime(buf, sizeof buf, "%a, %d %b %Y %H:%M:%S GMT", tmDate);

	date = (char*) malloc(size);

	strcpy(date,buf);

	return date;
}

/*
* Function: dateCompare
* Usage: if(dateCompare(tmDate,conditionalDate))
* --------------------------
* Compares the dates of the file requested with the date of the file on the server
* and returns true (1) or false (0) depending on if the file is up to date.
*/
int dateCompare(tm *tmDate, char *conditionalDate){
	char *fileTime;
	if (conditionalDate == NULL)
		return 0;

	fileTime = parseDate(tmDate);

	if ( (strcmp(fileTime, conditionalDate) ) == 0)
		return 1;
	else
		return 0;
}

/*
* Function: *getServerDate
* Usage: gmtDate = getServerDate()
* --------------------------
*  Returns a string of the current server date (GMT). 
*/
static char *getServerDate()
{

	time_t rawtime;
	struct tm *info;

	time( &rawtime );

	info = gmtime( &rawtime );

	return parseDate(info);
}

/*
* Function: getLocalDate
* Usage: date = getLocalDate();
* -----------
* This function returns a string with the current date in localtime
*/
static char *getLocalDate()
{

	time_t rawtime;
	struct tm *info;

	char buffer[100];
	char *date;

	size_t size;

	time(&rawtime);

	info = localtime(&rawtime);

	size = strftime(buffer, sizeof buffer, "[%d/%b/%Y:%H:%M:%S %z]", info);

	date = (char*)malloc(size);

	strcpy(date, buffer);

	return date;
}